/*
 *    Copyright 2010 Vanessa Williams
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/*
 * UnusualMotifs.java
 * Created on Mar 14, 2010
 */

package bioformatica.annotation;

import org.apache.commons.cli.*;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;

/**
 * Class to calculate the genome signature. That is, the observed frequency of dimers divided by
 * The expected frequency (which is the product of the observed frequency of each base in the
 * dimer.) This is called the odds ratio.
 * 
 * Takes the following options on the command-line:
 * <ul>
 * <li>-seq &lt;sequence file in FASTA format&gt;</li>
 * </ul>

 */
public class UnusualMotifs {

    private static final Logger LOG = Logger.getLogger(UnusualMotifs.class.getName());

    private String fastaFilename;

    public UnusualMotifs(String fastaFilename) {
        this.fastaFilename = fastaFilename;

    }

    /**
     * Caculate the ratio of the observed frequency of a k-mer and the product of the
     * observer ratios of each base in the k-mer.
     *
     * @param k length of k-mer
     * @return
     */
    public Map<String, Float> calculateOddsRatios(int k) {

        Map<String, Float> oddsRatios = new TreeMap<String, Float>();

        // get the base and dimer counts
        BaseCount baseCounter = new BaseCount(fastaFilename);
        KMerFrequency kmerCounter = new KMerFrequency(fastaFilename, k);
        Map<Character, Integer> baseCounts = baseCounter.getBaseCount(false);
        Map<String, Integer> kmerCounts = kmerCounter.kMerCounts();
        int baseCount = baseCounter.totalBaseCount();

        Set<String> kmers = kmerCounts.keySet();
        for (String kmer : kmers) {
            char[] bases = kmer.toCharArray();
            float dividend = (float)kmerCounts.get(kmer)/baseCount;
            float divisor=1;
            for (int i=0; i<k; i++) {
                divisor = divisor * (float)baseCounts.get(bases[i])/baseCount;
            }
            float oddsRatio = dividend/(divisor);
            oddsRatios.put(kmer, oddsRatio);
        }
        return oddsRatios;
    }

    /**
     * If k-2, the odds ratios make up a genome signature and the output is
     * a little bit different (including a 4x4 matrix.)
     *
     * @param oddsRatios output of calculateOddsRatios(2)
     * @return an output String for a nice printing of the genome signature.
     */
    public String genomeSignatureToString(Map<String, Float>oddsRatios) {
        StringBuilder sb = new StringBuilder();
        sb.append("genome signature = \n\n");
        sb.append("\t" + String.format("%.4f", oddsRatios.get("AA")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("AC")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("AG")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("AT")) + "\n");

        sb.append("\t" + String.format("%.4f", oddsRatios.get("CA")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("CC")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("CG")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("CT")) + "\n");

        sb.append("\t" + String.format("%.4f", oddsRatios.get("GA")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("GC")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("GG")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("GT")) + "\n");

        sb.append("\t" + String.format("%.4f", oddsRatios.get("TA")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("TC")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("TG")));
        sb.append("\t" + String.format("%.4f", oddsRatios.get("TT")) + "\n");
        return sb.toString();
    }

    /**
     * In the case where k != 2, print a more basic report, including k-mer and oddsRatio.
     *
     * @param oddsRatios oddsRatios calculated by calculateOddsRatios
     * @return a String suitable for output
     */
    public String oddsRatiosToString(Map<String, Float>oddsRatios) {
        StringBuilder sb = new StringBuilder();
        sb.append("k-mer odds ratios = \n\n");
        Set<String> kmers = oddsRatios.keySet();
        for (String kmer : kmers) {
            sb.append("\t" + kmer + "\t" + String.format("%.4f", oddsRatios.get(kmer)) + "\n");
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Options options = new Options();
        Option file = OptionBuilder.withArgName("file").
                                        hasArg().
                                        withDescription("FASTA file name").
                                        create("seq");
        Option command = OptionBuilder.withArgName("n").
                                        hasArg().
                                        withDescription("Specify the number of contiguous bases to test frequencies for").
                                        create("k");
        options.addOption(command);
        options.addOption(file);
        CommandLineParser parser = new GnuParser();
        try {
            // parse the command line arguments
            CommandLine line = parser.parse( options, args );
            if (line.hasOption("seq")) {
                String fasta = line.getOptionValue("seq");
                UnusualMotifs instance = new UnusualMotifs(fasta);
                if (line.hasOption("k")) {
                    String k = line.getOptionValue("k");
                    Map<String, Float> oddsRatios = instance.calculateOddsRatios(Integer.parseInt(k));
                    String output=null;
                    if (k.equals("2")) {
                        output = instance.genomeSignatureToString(oddsRatios);
                    }
                    else {
                        output = instance.oddsRatiosToString(oddsRatios);
                    }
                    System.out.println(output);
                }
            }

        } catch (ParseException e) {
            LOG.exiting("Can't parse command-line", e.getMessage());
            System.exit(1);
        }
    }
}
